#pragma once

#include <algorithm>
#include <stdexcept>
#include <string>

#include "common/macros.h"
#include "common/util/string_util.h"
#include "type/abstract_pool.h"
#include "type/boolean_type.h"
#include "type/numeric_type.h"
#include "type/timestamp_type.h"
#include "type/value.h"
#include "type/varlen_type.h"

//===--------------------------------------------------------------------===//
// Value Factory
//===--------------------------------------------------------------------===//

class ValueFactory {
   public:
    static inline Value Clone(const Value &src, __attribute__((__unused__)) AbstractPool *dataPool = nullptr) {
        return src.Copy();
    }

    static inline Value GetIntegerValue(int32_t value) { return Value(TypeId::INTEGER, value); }

    static inline Value GetFloatValue(float value) { return Value(TypeId::FLOAT, value); }

    static inline Value GetTimestampValue(int64_t value) { return Value(TypeId::TIMESTAMP, value); }

    static inline Value GetBooleanValue(CmpBool value) {
        return Value(TypeId::BOOLEAN, value == CmpBool::CmpNull ? DATABASE_BOOLEAN_NULL : static_cast<int8_t>(value));
    }

    static inline Value GetBooleanValue(bool value) { return Value(TypeId::BOOLEAN, static_cast<int8_t>(value)); }

    static inline Value GetBooleanValue(int8_t value) { return Value(TypeId::BOOLEAN, value); }

    static inline Value GetVarcharValue(const char *value, bool manage_data,
                                        __attribute__((__unused__)) AbstractPool *pool = nullptr) {
        auto len = static_cast<uint32_t>(value == nullptr ? 0U : strlen(value) + 1);
        return GetVarcharValue(value, len, manage_data);
    }

    static inline Value GetVarcharValue(const char *value, uint32_t len, bool manage_data,
                                        __attribute__((__unused__)) AbstractPool *pool = nullptr) {
        return Value(TypeId::VARCHAR, value, len, manage_data);
    }

    static inline Value GetVarcharValue(const std::string &value,
                                        __attribute__((__unused__)) AbstractPool *pool = nullptr) {
        return Value(TypeId::VARCHAR, value);
    }

    static inline Value GetNullValueByType(TypeId type_id) {
        Value ret_value;
        switch (type_id) {
            case TypeId::BOOLEAN:
                ret_value = GetBooleanValue(DATABASE_BOOLEAN_NULL);
                break;
            case TypeId::INTEGER:
                ret_value = GetIntegerValue(DATABASE_INT32_NULL);
                break;
            case TypeId::VARCHAR:
                ret_value = GetVarcharValue(nullptr, false, nullptr);
                break;
            default: {
                throw Exception(ExceptionType::UNKNOWN_TYPE, "Attempting to create invalid null type");
            }
        }
        return ret_value;
    }

    static inline Value GetZeroValueByType(TypeId type_id) {
        std::string zero_string("0");

        switch (type_id) {
            case TypeId::BOOLEAN:
                return GetBooleanValue(false);
            case TypeId::INTEGER:
                return GetIntegerValue(0);
            case TypeId::VARCHAR:
                return GetVarcharValue(zero_string);
            default:
                break;
        }
        throw Exception(ExceptionType::UNKNOWN_TYPE, "Unknown type for GetZeroValueType");
    }

    static inline Value CastAsInteger(const Value &value) {
        if (Type::GetInstance(TypeId::INTEGER)->IsCoercableFrom(value.GetTypeId())) {
            if (value.IsNull()) {
                return ValueFactory::GetIntegerValue(DATABASE_INT32_NULL);
            }
            switch (value.GetTypeId()) {
                case TypeId::INTEGER:
                    return ValueFactory::GetIntegerValue(value.GetAs<int32_t>());
                case TypeId::VARCHAR: {
                    std::string str = value.ToString();
                    int32_t integer = 0;
                    try {
                        integer = stoi(str);
                    } catch (std::out_of_range &e) {
                        throw Exception(ExceptionType::OUT_OF_RANGE, "Numeric value out of range.");
                    } catch (std::invalid_argument &e) {
                        throw Exception("Invalid input syntax for integer: \'" + str + "\'");
                    }

                    if (integer > DATABASE_INT32_MAX || integer < DATABASE_INT32_MIN) {
                        throw Exception(ExceptionType::OUT_OF_RANGE, "Numeric value out of range.");
                    }
                    return ValueFactory::GetIntegerValue(integer);
                }
                default:
                    break;
            }
        }
        throw Exception(Type::GetInstance(value.GetTypeId())->ToString(value) + " is not coercable to INTEGER.");
    }

    static inline Value CastAsVarchar(const Value &value) {
        if (Type::GetInstance(TypeId::VARCHAR)->IsCoercableFrom(value.GetTypeId())) {
            if (value.IsNull()) {
                return ValueFactory::GetVarcharValue(nullptr, false);
            }
            switch (value.GetTypeId()) {
                case TypeId::BOOLEAN:
                case TypeId::INTEGER:
                case TypeId::VARCHAR:
                    return ValueFactory::GetVarcharValue(value.ToString());
                default:
                    break;
            }
        }
        throw Exception(Type::GetInstance(value.GetTypeId())->ToString(value) + " is not coercable to VARCHAR.");
    }

    static inline Value CastAsTimestamp(const Value &value) {
        if (Type::GetInstance(TypeId::TIMESTAMP)->IsCoercableFrom(value.GetTypeId())) {
            if (value.IsNull()) {
                return ValueFactory::GetTimestampValue(DATABASE_TIMESTAMP_NULL);
            }
            switch (value.GetTypeId()) {
                case TypeId::TIMESTAMP:
                    return ValueFactory::GetTimestampValue(value.GetAs<uint64_t>());
                case TypeId::VARCHAR: {
                    std::string str = value.ToString();
                    if (str.length() == 22) {
                        str = str.substr(0, 19) + ".000000" + str.substr(19, 3);
                    }
                    if (str.length() != 29) {
                        throw Exception("Timestamp format error.");
                    }
                    if (str[10] != ' ' || str[4] != '-' || str[7] != '-' || str[13] != ':' || str[16] != ':' ||
                        str[19] != '.' || (str[26] != '+' && str[26] != '-')) {
                        throw Exception("Timestamp format error.");
                    }
                    bool is_digit[29] = {true,  true, true, true,  false, true, true,  false, true, true,
                                         false, true, true, false, true,  true, false, true,  true, false,
                                         true,  true, true, true,  true,  true, false, true,  true};
                    for (int i = 0; i < 29; i++) {
                        if (is_digit[i]) {
                            if (str[i] < '0' || str[i] > '9') {
                                throw Exception("Timestamp format error.");
                            }
                        }
                    }
                    int tz = 0;
                    uint32_t year = 0;
                    uint32_t month = 0;
                    uint32_t day = 0;
                    uint32_t hour = 0;
                    uint32_t min = 0;
                    uint32_t sec = 0;
                    uint32_t micro = 0;
                    uint64_t res = 0;
                    if (sscanf(str.c_str(), "%4u-%2u-%2u %2u:%2u:%2u.%6u%3d", &year, &month, &day, &hour, &min, &sec,
                               &micro, &tz) != 8) {
                        throw Exception("Timestamp format error.");
                    }
                    if (year > 9999 || month > 12 || day > 31 || hour > 23 || min > 59 || sec > 59 || micro > 999999 ||
                        day == 0 || month == 0) {
                        throw Exception(ExceptionType::OUT_OF_RANGE, "Timestamp value out of range.");
                    }
                    uint32_t max_day[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                    uint32_t max_day_lunar[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
                        if (day > max_day_lunar[month]) {
                            throw Exception(ExceptionType::OUT_OF_RANGE, "Timestamp value out of range.");
                        }
                    } else if (day > max_day[month]) {
                        throw Exception(ExceptionType::OUT_OF_RANGE, "Timestamp value out of range.");
                    }
                    uint32_t timezone = tz + 12;
                    if (tz > 26) {
                        throw Exception("Timestamp format error.");
                    }
                    res += month;
                    res *= 32;
                    res += day;
                    res *= 27;
                    res += timezone;
                    res *= 10000;
                    res += year;
                    res *= 100000;
                    res += hour * 3600 + min * 60 + sec;
                    res *= 1000000;
                    res += micro;
                    return ValueFactory::GetTimestampValue(res);
                }
                default:
                    break;
            }
        }
        throw Exception(Type::GetInstance(value.GetTypeId())->ToString(value) + " is not coercable to TIMESTAMP.");
    }

    static inline Value CastAsBoolean(const Value &value) {
        if (Type::GetInstance(TypeId::BOOLEAN)->IsCoercableFrom(value.GetTypeId())) {
            if (value.IsNull()) {
                return ValueFactory::GetBooleanValue(DATABASE_BOOLEAN_NULL);
            }
            switch (value.GetTypeId()) {
                case TypeId::BOOLEAN:
                    return ValueFactory::GetBooleanValue(value.GetAs<int8_t>());
                case TypeId::VARCHAR: {
                    std::string str = value.ToString();
                    std::transform(str.begin(), str.end(), str.begin(), ::tolower);
                    if (str == "true" || str == "1" || str == "t") {
                        return ValueFactory::GetBooleanValue(true);
                    }
                    if (str == "false" || str == "0" || str == "f") {
                        return ValueFactory::GetBooleanValue(false);
                    }
                    throw Exception("Boolean value format error.");
                }
                default:
                    break;
            }
        }
        throw Exception(Type::GetInstance(value.GetTypeId())->ToString(value) + " is not coercable to BOOLEAN.");
    }
};
